#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string>
#include <map>
#include <stdexcept>

#include "Base/String.h"
#include "Thread/Thread.h"

#include "Internal.h"

void FileSystem_Init()
{
	assert(NULL == Impl);
	Impl = new FileSystemImpl;
	atexit(FileSystem_Free);
	Syncer = new Thread(FileSystem_Syncer);
};

void FileSystem_Free()
{
	if(NULL != Impl)
	{
		delete Syncer;
		Syncer = NULL;

		FileSystem_Sync();

		FileMap::iterator It = Impl->myFileMap.begin();
		FileMap::iterator ItEnd = Impl->myFileMap.end();

		for(; It != ItEnd; ++It)
			close(It->second.iFD);

		delete Impl;
		Impl = NULL;
	};
};

bool Open(FileHandle * file, const char * filePath, int modes)
{
	assert((NULL != Impl)&&(NULL != file)&&(NULL != filePath));

	bool bResult = true;
	do
	{
		const std::string sFilePath = filePath;
		FileMap::iterator It = Impl->myFileMap.find(filePath);
		if(Impl->myFileMap.end() == It)
		{
			BFile newFile;
			int flags = O_RDWR;
			if((modes & FM_Create) > 0)
				flags |= O_CREAT;

			newFile.iFD = open(filePath, flags, 0644);
			if(-1 == newFile.iFD)
			{
				bResult = false;
				break;
			};
			newFile.iSize = lseek(newFile.iFD, 0, SEEK_END);
			newFile.iUseCount = 1;
			It = Impl->myFileMap.insert(std::make_pair(sFilePath, newFile)).first;
		};

		file->pFile = &(It->second);
		file->iCurPos = 0;
	} while(0);

	return bResult;
};

void Close(FileHandle * file)
{
	assert(NULL != file);
	--(file->pFile->iUseCount);
	/* TODO: thats all?? */
};

SSizeType Read(FileHandle * file, void * data, SizeType len)
{
	assert(NULL != file);
	const SSizeType iResult = PRead(file, data, len, file->iCurPos);
	if(0 < iResult)
		file->iCurPos += iResult;
	return iResult;
};

SSizeType Write(FileHandle * file, const void * data, SizeType len)
{
	assert(NULL != file);
	const SSizeType iResult = PWrite(file, data, len, file->iCurPos);
	if(0 < iResult)
		file->iCurPos += iResult;
	return iResult;
};

SSizeType PRead(FileHandle * file, void * data, SizeType len, SizeType offset)
{
	assert((NULL != file)&&(NULL != data));
	SSizeType iDone = 0;
	SizeType iPos = offset;
	SSizeType iRemain = len;
	do
	{
		const SizeType iSector = iPos / MBLOCK_DEFAULT_SIZE;
		const SizeType iBlockOffset = iPos % MBLOCK_DEFAULT_SIZE;
		const MBlock & block = FileSystem_GetBlock(file, iSector);
		const SSizeType iPosibleDone = block.getRealSize() - iBlockOffset;
		if(0 >= iPosibleDone) /* nothing to read more */
			break;

		const SizeType iDoPerBlock = (iRemain < iPosibleDone ? iRemain : iPosibleDone);
		if(false == block.read((char*)data + iDone, iBlockOffset, iDoPerBlock))
		{ /* erro r occuired while read data from block! */
			iDone = -1; 
			break;
		};
		iRemain -= iDoPerBlock;
		iPos += iDoPerBlock;
		iDone += iDoPerBlock;
	}
	while(iRemain > 0);

	return iDone;
};

SSizeType PWrite(FileHandle * file, const void * data, SizeType len, SizeType offset)
{
	assert((NULL != file)&&(NULL != data));

	SSizeType iDone = 0;
	SizeType iPos = offset;
	SSizeType iRemain = len;
	do
	{
		const SizeType iSector = iPos / MBLOCK_DEFAULT_SIZE;
		const SizeType iBlockOffset = iPos % MBLOCK_DEFAULT_SIZE;
		MBlock & block = FileSystem_GetBlock(file, iSector);
		const SSizeType iPosibleDone = MBLOCK_DEFAULT_SIZE - iBlockOffset;
		if(0 > iPosibleDone) /* nothing to read more */
			break;

		const SizeType iDoPerBlock = (iRemain < iPosibleDone ? iRemain : iPosibleDone);
		if(false == block.write((char*)data + iDone, iBlockOffset, iDoPerBlock))
		{ /* error occuired while read data from block! */
			iDone = -1;
			break;
		};
		iRemain -= iDoPerBlock;
		iPos += iDoPerBlock;
		iDone += iDoPerBlock;
	}
	while(iRemain > 0);

	/* update RealSize - need for Seek function and others. */
	if(-1 != iDone)
		file->pFile->iSize = std::max(offset + iDone, file->pFile->iSize);

	return iDone;
};

bool Truncate(FileHandle * file, SizeType size)
{
	assert(NULL != file);

	if(-1 == ftruncate(file->pFile->iFD, size))
		return false;

	if(file->pFile->iSize > size)
	{	/* destroy all blocks, how not needed more. */
		MBlockMap::iterator It = file->pFile->myBlockMap.find((size/MBLOCK_DEFAULT_SIZE) + 1);
		file->pFile->myBlockMap.erase(It, file->pFile->myBlockMap.end());
	};

	file->pFile->iSize = size;

	return true;
};

SizeType Seek(FileHandle * file, SSizeType offset, int whence)
{
	assert(NULL != file);
	SSizeType iNewPos = file->iCurPos;
	switch(whence)
	{ 
		case SEEK_SET: 
			iNewPos = offset;
			break;
		case SEEK_CUR: 
			iNewPos += offset;
			break;
		case SEEK_END: 
			iNewPos = offset + file->pFile->iSize;
			break;
		default:
			return -1;
	};

	if(iNewPos < 0)
		file->iCurPos = 0;
	else if(iNewPos > static_cast<SSizeType>(file->pFile->iSize))
		file->iCurPos = file->pFile->iSize;
	else
		file->iCurPos = iNewPos;

	return file->iCurPos;
};

